# -*- coding: utf-8 -*-
# Evaluator.py
from EvaluationData import EvaluationData
from EvaluatedAlgorithm import EvaluatedAlgorithm


class Evaluator:
    """
    封装推荐算法的评估方法。
    """

    def __init__(self, dataset, rankings):
        """
        初始化 Evaluator 类。

        :param dataset: 原始数据集，通常是一个 Surprise 数据集。
        :param rankings: 项目的流行度排名信息，用于后续计算覆盖率等指标。
        """
        # 获取数据集
        ed = EvaluationData(dataset, rankings)
        self.dataset = ed

        # 所有待评估的算法
        self.algorithms = []

    def AddAlgorithm(self, algorithm, name: str):
        """
        添加待评估的推荐算法。

        :param algorithm: 推荐算法实例。
        :param name: 算法名称。
        """
        # 算法封装为 EvaluatedAlgorithm 类
        alg = EvaluatedAlgorithm(algorithm, name)
        # 添加到数组
        self.algorithms.append(alg)

    def Evaluate(self, doTopN: bool):
        """
        评估所有添加的推荐算法的性能。

        :param doTopN: 是否进行 Top-N 推荐评估。
        """
        results = {}
        for algorithm in self.algorithms:
            print("Evaluating ", algorithm.GetName(), "...")
            results[algorithm.GetName()] = algorithm.Evaluate(self.dataset, doTopN)

        # 打印结果
        print("\n")

        if doTopN:
            print("{:<10} {:<10} {:<10} {:<10} {:<10} {:<10} {:<10} {:<10} {:<10}".format(
                "Algorithm", "RMSE", "MAE", "HR", "CHR", "ARHR", "Coverage", "Diversity", "Novelty"))
            for name, metrics in results.items():
                print("{:<10} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f}".format(
                    name, metrics["RMSE"], metrics["MAE"], metrics["HR"], metrics["CHR"], metrics["ARHR"],
                    metrics["Coverage"], metrics["Diversity"], metrics["Novelty"]))
        else:
            print("{:<10} {:<10} {:<10}".format("Algorithm", "RMSE", "MAE"))
            for name, metrics in results.items():
                print("{:<10} {:<10.4f} {:<10.4f}".format(name, metrics["RMSE"], metrics["MAE"]))

        print("\nLegend:\n")
        print("RMSE:        Root Mean Squared Error. Lower values mean better accuracy.")
        print("MAE:         Mean Absolute Error. Lower values mean better accuracy.")
        if doTopN:
            print("HR:        Hit Rate; how often we are able to recommend a left-out rating. Higher is better.")
            print(
                "cHR:        Cumulative Hit Rate; hit rate, confined to ratings above a certain threshold. Higher is better.")
            print(
                "ARHR:        Average Reciprocal Hit Rank; hit rate that takes the ranking into account. Higher is better.")
            print(
                "Coverage: Ratio of users for whom recommendations above a certain threshold exist. Higher is better.")
            print(
                "Diversity: 1-S, where s is the average similarity score between every possible pair of recommendations")
            print("              for a given user. Higher means more diverse.")
            print("Novelty:        Average popularity rank of recommended items. Higher means more novel.")

    def SampleTopNRecs(self, ml, testSubject: int = 85, k: int = 10):
        """
        遍历所有已添加的算法，对用户未评分的项目进行预测。
        输出预测评分最高的前 k 个项目及其评分。

        :param ml: 包含电影信息的对象，通常具有 getMovieName 方法。
        :param testSubject: 用户ID，默认为 85。
        :param k: Top-N 推荐的数量，默认为 10。
        """
        for algo in self.algorithms:
            print("\nUsing recommender ", algo.GetName())

            print("\nBuilding recommendation model...")
            trainSet = self.dataset.GetFullTrainSet()
            algo.GetAlgorithm().fit(trainSet)

            print("Computing recommendations...")
            # 用户尚未评分的电影集 AntiTestSet
            testSet = self.dataset.GetAntiTestSetForUser(testSubject)

            # 对未看过的电影进行评分预测
            predictions = algo.GetAlgorithm().test(testSet)

            recommendations = []
            print("\nWe recommend:")
            for userID, movieID, actualRating, estimatedRating in predictions:
                intMovieID = int(movieID)
                recommendations.append((intMovieID, estimatedRating))

            # 根据评分排序
            recommendations.sort(key=lambda x: x[1], reverse=True)

            for ratings in recommendations[:k]:
                print(ml.getMovieName(ratings[0]), ratings[1])
